# -*- coding: UTF-8 -*-
import tkinter as tk
from tkinter import ttk, scrolledtext
from Code.Utils.sc_func import Common, JsonParser
from Code.View.sc_global import Global
from Code.View.sc_base import AppBase, wrapper_exception, PageDataError, UpdateResultError
from Code.View.sc_page import PageCtrl, Attribution, Action
from Code.View.sc_login import NewLoginTop
from Code.View.sc_about import AboutTop
from Code.View.sc_settings import SettingsTop
from Code.View.sc_sftp import FileTreeTop
from Code.View.sc_screenshot import ScreenShot
from Code.View.sc_module import (WidgetTip,
                                 ProgressBar,
                                 ScrollFrame,
                                 InfoText,
                                 TitleFrame,
                                 ToolBar,
                                 MenuBar,
                                 NavigationBar
                                 )
from Code.View.Datamation.sc_provide import view_gate

language_data = view_gate.query_language_data


def is_pure_mode():
    return view_gate.query_pure_mode_data()


class MainApp(AppBase):
    """ 主界面 """
    def __init__(self, app_gate, menu, master):
        self.app_gate = app_gate
        self.master = master
        self.menu = menu
        self.ip_list = view_gate.query_login_ip_data()
        self.trees_data = []     # 导航栏数据
        self.pages_data = {}     # 界面数据
        self.page_ctrl = None
        self.navigationbar = None
        self.infobar = None
        self.selectionbar = None
        self.printoutbar = None
        self.handler = None

    @wrapper_exception
    def init_trees_pages_data(self):
        def parse_trees(_tree):
            name = _tree['Text'].strip()
            img = _tree['Image']
            page = _tree['Page']
            sub = _tree["SubTree"]
            if name in self.pages_data:
                raise PageDataError("Trees.Text {} {}".format(language_data('CONFLICT'), name))
            val = []
            if isinstance(sub, list) and sub:
                for _sub in sub:
                    val.append(parse_trees(_sub))
            else:
                # 这里对query的数据做了侵入修改
                # 加上Image, 为了后面加入ToolBar
                page['Image'] = img
                self.pages_data[name] = page
            return name, img, val

        for tree in view_gate.query_tree_view_data():
            self.trees_data.append(parse_trees(tree))

    def init_frames(self):
        # 工具栏
        tool_fm = tk.Frame(self.master)
        tool_fm.pack(fill='x')
        # 主框架
        paned_win_outer = ttk.Panedwindow(self.master,
                                          orient=tk.HORIZONTAL,
                                          width=Global.G_MAIN_WIN_WIDTH,
                                          height=Global.G_MAIN_WIN_HEIGHT)
        win_style = {'master': paned_win_outer,
                     'height': Global.G_MAIN_WIN_HEIGHT}
        tree_win = tk.Frame(width=Global.G_MAIN_TREE_WIDTH, **win_style)
        oper_win = tk.Frame(width=Global.G_MAIN_OPER_WIDTH, **win_style)
        tree_win.pack(side='left', fill='both')
        tree_win.pack_propagate(0)
        oper_win.pack(side='right', fill='both')
        oper_win.pack_propagate(0)
        paned_win_outer.add(tree_win, weight=1)
        paned_win_outer.add(oper_win, weight=3)
        paned_win_outer.pack(fill='both')
        tree_fm = TitleFrame(tree_win,
                             width=Global.G_MAIN_TREE_WIDTH + 100,  # 用于拖动窗口扩展
                             height=Global.G_MAIN_WIN_HEIGHT,
                             title=language_data('NAVIGATION_BAR')
                             ).master()
        paned_win_inner = ttk.Panedwindow(oper_win,
                                          orient=tk.VERTICAL,
                                          width=Global.G_MAIN_OPER_WIDTH,
                                          height=Global.G_MAIN_WIN_HEIGHT)
        fm_style = {'master': paned_win_inner,
                    'width': Global.G_MAIN_OPER_WIDTH}
        oper_fm = tk.Frame(height=Global.G_MAIN_OPER_HEIGHT, **fm_style)
        text_fm = tk.Frame(height=Global.G_MAIN_TEXT_HEIGHT, **fm_style)
        oper_fm.pack(fill='both')
        oper_fm.pack_propagate(0)
        text_fm.pack(fill='both')
        text_fm.pack_propagate(0)
        paned_win_inner.add(oper_fm, weight=3)
        paned_win_inner.add(text_fm, weight=1)
        paned_win_inner.pack(fill='both')
        fm_style = {'master': oper_fm,
                    'height': Global.G_MAIN_OPER_HEIGHT + 200,
                    'background': Global.G_MAIN_PAGE_COLOR}
        page_fm = tk.LabelFrame(width=Global.G_MAIN_PAGE_WIDTH, **fm_style)
        page_fm.pack(side='left', fill='both')
        ips_fm = tk.Frame(width=Global.G_MAIN_IPS_WIDTH, **fm_style)
        ips_fm.pack(side='left', fill='both')
        ips_fm.pack_propagate(0)
        fm_style = {'master': text_fm,
                    'height': Global.G_MAIN_TEXT_HEIGHT}
        info_fm = tk.Frame(width=Global.G_MAIN_INFO_WIDTH, **fm_style)
        note_fm = tk.Frame(width=Global.G_MAIN_NOTE_WIDTH, **fm_style)
        info_fm.pack(side='left', fill='both')
        info_fm.pack_propagate(0)
        note_fm.pack(side='left', fill='both')
        note_fm.pack_propagate(0)
        # 底部信息栏
        foot_fm = tk.LabelFrame(self.master)
        foot_fm.pack(fill='x')
        return tool_fm, tree_fm, page_fm, ips_fm, info_fm, note_fm, foot_fm

    def pack_frames(self, frames):
        tool_fm, tree_fm, page_fm, ips_fm, info_fm, note_fm, foot_fm = frames
        # if not is_pure_mode():   #  不布局menu时,移动窗体后窗体会变大?
        self.pack_menubar(self.menu)
        self.pack_navigationbar(tree_fm)
        self.pack_pagebar(page_fm)
        if not is_pure_mode():
            self.pack_toolbar(tool_fm)
        self.pack_selectionbar(ips_fm)
        self.pack_infobar(info_fm)
        self.pack_printoutbar(note_fm)
        self.pack_footbar(foot_fm)

    def pack_navigationbar(self, master):
        self.navigationbar = NavigationBar(master, self.trees_data, self.switch_page)

    def pack_menubar(self, master):
        items = [(language_data('FILE'), ['-',
                     (language_data('EXIT'), 'MENU_EXIT')]),
                 (language_data('SETTINGS'), [
                     (language_data('PREFERENCES'), 'MENU_SET'), '-',
                     (language_data('THEME'), [
                         (language_data('BRIGHT'), 'MENU_WHITE'),
                         (language_data('DARK'), 'MENU_BLACK')])]),
                 (language_data('TOOLS'), [
                     (language_data('NEW_LOGIN'), 'MENU_LOGIN'),
                     (language_data('SCREEN_CAPTURE'), 'MENU_SCREEN'),
                     (language_data('SFTP_UP_DOWN'), 'MENU_SFTP')]),
                 (language_data('HELP'), [
                     (language_data('HELP_DOC'), 'MENU_HELP'),
                     (language_data('ABOUT_SOFTWARE'), 'MENU_INFO')])
                 ]
        if is_pure_mode():
            items = [(language_data('FILE'), ['-',
                         (language_data('EXIT'), 'MENU_EXIT')]),
                     (language_data('HELP'), [
                         (language_data('ABOUT_SOFTWARE'), 'MENU_INFO')])
                     ]
        MenuBar(master, items, self.press_bar_handle)

    @wrapper_exception
    def pack_toolbar(self, master):
        start = [(language_data('EXPAND_NAVIGATION'), 'TB_EXPAND', 1),
                 (language_data('ABOVE_PAGE'), 'TB_LAST', 1),
                 (language_data('BELOW_PAGE'), 'TB_NEXT', 1)]
        end = [(language_data('SCREEN_CAPTURE'), 'TB_SCREEN', 1),
               (language_data('SFTP_UP_DOWN'), 'TB_SFTP', 1),
               (language_data('NEW_LOGIN'), 'TB_LOGIN', 1),
               (language_data('SETTINGS'), 'TB_SET', 1),
               (language_data('HELP_DOC'), 'TB_HELP', 1),
               (language_data('ABOUT_SOFTWARE'), 'TB_INFO', 1)
               ]
        from_tree = self.page_ctrl.get_toolbar_icons()
        toolbar_list = start + from_tree + end
        ToolBar(master, toolbar_list, self.press_bar_handle)

    def pack_pagebar(self, master):
        self.page_ctrl = PageCtrl(master, self.pages_data)
        self.handler = MainHandler()
        self.handler.init_page_ctrl(self.page_ctrl)

    def pack_selectionbar(self, master):
        fm = TitleFrame(master, width=Global.G_MAIN_IPS_WIDTH, height=Global.G_MAIN_OPER_HEIGHT,
                        title=language_data('SELECTION_BAR')).master()
        self.selectionbar = SelectionBar(fm, self.ip_list, self.handler.execute_handle,
                                         self.handler.login_instance_handle)
        self.selectionbar.disable_exec_button(True)
        self.handler.init_selectionbar(self.selectionbar)

    def pack_infobar(self, master):
        fm = TitleFrame(master, width=Global.G_MAIN_INFO_WIDTH, height=Global.G_MAIN_TEXT_HEIGHT + 200,
                        title=language_data('INFO_BAR')).master()
        self.infobar = InfoText(fm)

    def pack_printoutbar(self, master):
        fm = TitleFrame(master, width=Global.G_MAIN_NOTE_WIDTH, height=Global.G_MAIN_TEXT_HEIGHT + 200,
                        title=language_data('PRINTOUT_BAR')).master()
        self.printoutbar = PrintOutBar(fm, self.ip_list)
        self.handler.init_printoutbar(self.printoutbar)

    def pack_footbar(self, master):
        def update_info():
            while True:
                Common.sleep(60)
                sec = Common.time() - self.app_gate.start_time
                info = "{} {}h {}m".format(language_data('SOFTWARE_RUN_TIME'), int(sec / 3600), int((sec % 3600) / 60))
                text = "{}      {}".format(use_time, info)
                label['text'] = text

        use_time = '{} {:.3f}s'.format(language_data('SOFTWARE_STARTUP_TIME'), self.app_gate.use_time)
        run_time = '{} 0h 0m'.format(language_data('SOFTWARE_RUN_TIME'))
        Common.create_thread(update_info)
        label = tk.Label(master, text="{}      {}".format(use_time, run_time))
        label.pack()

    def switch_page(self, page_name):
        def back_to_page():
            if curr_page:
                self.navigationbar.selection(curr_page)

        curr_page = self.page_ctrl.curr_page
        if page_name == curr_page or page_name not in self.pages_data:
            return
        if view_gate.query_running_task_data():
            back_to_page()
            WidgetTip.error(language_data('TASK_RUNNING_PLS_WAIT_OR_CANCEL'))
            return
        if self.handler.is_loopping:
            back_to_page()
            WidgetTip.error(language_data('PERIODIC_EXECUTE', 'TASK_RUNNING_PLS_WAIT_OR_CANCEL'))
            return
        # 如果界面切换失败,回到上一个可用界面
        if not self.page_ctrl.switch_page(page_name):
            self.page_ctrl.curr_page = None
            return back_to_page()
        # 选择栏显示处理
        self.selectionbar.disable_exec_button(Attribution.has_no_start_button)
        self.selectionbar.disable_root_checkbox(Attribution.has_be_run_in_root, Attribution.has_not_run_in_root)
        # 预处理
        if Attribution.has_pre_handle:
            is_root = self.selectionbar.get_is_in_root()
            data = ('START', 'pre', view_gate.query_login_ip_data(), (is_root, 0))
            self.handler.execute_handle(data)

    def press_bar_handle(self, key):
        if key == 'TB_EXPAND':
            self.navigationbar.expand_trees()
        elif key in ['MENU_INFO', "TB_INFO"]:
            AboutTop().show()
        elif key in ['MENU_SCREEN', 'TB_SCREEN']:
            ScreenShot()
        elif key in ['MENU_LOGIN', 'TB_LOGIN']:
            self.handler.login_instance_handle(('NEW',))
        elif key in ['MENU_SET', 'TB_SET']:
            SettingsTop().show()
        elif key in ['MENU_SFTP', 'TB_SFTP']:
            FileTreeTop().show()
            self.app_gate.hide()
        elif key == 'MENU_EXIT':
            self.app_gate.close()
        elif key == 'TB_LAST':
            self.navigationbar.prev()
        elif key == 'TB_NEXT':
            self.navigationbar.next()
        elif key in ["MENU_WHITE", "MENU_BLACK"]:
            WidgetTip.info(language_data('COMING_SOON'))
        elif key in ['MENU_HELP', 'TB_HELP']:
            pdf = view_gate.query_env_define_data('G_PDF_PATH')
            if not Common.is_file(pdf):
                WidgetTip.error('{} {}'.format(pdf, language_data('NOT_EXIST')))
                return
            Common.create_thread(lambda: Common.system_open(pdf))
        else:
            self.navigationbar.selection(key)

    def init_windows(self):
        self.init_trees_pages_data()
        if is_pure_mode():
            Global.G_APP_HEIGHT = Global.G_APP_HEIGHT - 30
        self.app_gate.resize(Global.G_APP_WIDTH, Global.G_APP_HEIGHT)
        self.pack_frames(self.init_frames())
        self.page_ctrl.switch_default()

    @wrapper_exception
    def new_login_result(self, data):
        """ 新建登录结果反馈回调函数 """
        ip, status, info = data
        self.selectionbar.set_status(ip, status)      # 选择栏新增IP状态
        self.printoutbar.append_instance(ip)          # 输出栏新增IP标签页
        self.page_ctrl.append_widgets_tab(ip)         # Page操作界面各控件新增IP项
        NewLoginTop().close()
        if status == 'SUCCESS':
            WidgetTip.info(info)
        else:   # 'FAILED'
            WidgetTip.error(info)

    def update_progress_data(self, data):
        if not self.page_ctrl.is_alive():
            return
        ip, progress, state = data
        self.selectionbar.set_progress(ip, progress, state)

    def update_infotext_data(self, data):
        """ 任务执行过程进度打印回调函数 """
        info, state = data
        state = 'INFO' if state else 'ERROR'
        color = Global.G_INFOWIN_LEVEL_COLOR[state]
        info = "{} {} {}".format(Common.get_time(), state, info)
        self.infobar.insert_text(info, color=color, see_end=True, store=True)

    def update_printout_data(self, data):
        ip, info = data
        self.printoutbar.update(ip, info)

    @wrapper_exception
    def update_widgets_data(self, data):
        ip, values = data
        if not values:
            raise UpdateResultError("Not got widget values")
        self.page_ctrl.update_widgets_values(ip, JsonParser.loads(values))
        # 更新 plot widgets 的图表 #
        self.handler.fill_plot_widget_image(ip)

    def update_ip_state(self, data):
        """ IP状态变更反馈回调函数 """
        for ip, item in data.items():
            self.selectionbar.set_status(ip, item['STATE'])


class MainHandler(object):
    def __init__(self):
        self.page_ctrl = None
        self.selectionbar = None
        self.printoutbar = None
        self.is_loopping = False
        self.tmp_img_keys = []

    def init_page_ctrl(self, page_ctrl):
        self.page_ctrl = page_ctrl

    def init_selectionbar(self, selectionbar):
        self.selectionbar = selectionbar

    def init_printoutbar(self, printoutbar):
        self.printoutbar = printoutbar

    def _loop_timer(self, period, ips, flag, root, script):
        if period:
            self.is_loopping = True
            start_time = Common.time()
            while self.is_loopping:
                Common.sleep(1)
                left_sec = period - int(Common.time() - start_time)
                left_sec = 0 if left_sec < 0 else left_sec
                self.selectionbar.update_timer(left_sec)
                if left_sec == 0:
                    self._task_start(ips, flag, root, script)
                    start_time = Common.time()

    def _task_start(self, ips, flag, root, script):
        if (not script) and (not self.page_ctrl.get_plot_widgets()):
            raise PageDataError('Script {}'.format(language_data('CANT_NULL')))
        if flag == 'pre':
            params = {ip: flag for ip in ips}
            uploads = {ip: None for ip in ips}
        else:
            params = {}
            Action.upload_files = {}
            for ip in ips:
                params[ip] = "{} {}".format(flag, Common.base64_encode(
                    JsonParser.dumps(self.page_ctrl.get_widgets_values(ip))))
            uploads = Action.upload_files
        # 清理控件
        # 1. setable控件的值
        # 2. plot图表数据
        self.page_ctrl.clear_widgets_values()
        for key in self.tmp_img_keys:
            view_gate.del_image_data.set_data((key,))
        view_gate.start_exec_data.set_data((ips, script, root, params, uploads))

    def _task_stop(self, ips, script):
        self.is_loopping = False
        if not script:
            raise PageDataError('Script {}'.format(language_data('CANT_NULL')))
        view_gate.stop_exec_data.set_data((ips, script))

    @wrapper_exception
    def execute_handle(self, data):
        """ 选择栏点击'执行'和'取消'的逻辑处理 """
        oper, flag, ips, opts = data
        if not ips:
            WidgetTip.warn(language_data('PLS_SELECT_IP'))
            return
        root, loop = opts
        script = self.page_ctrl.get_page_script()
        if oper == 'START':
            if self.is_loopping:
                return
            self._task_start(ips, flag, root, script)
            if loop:
                Common.create_thread(self._loop_timer, args=(loop, ips, flag, root, script))
        else:    # 'STOP'
            self._task_stop(ips, script)

    def fill_plot_widget_image(self, ip):
        """ 两种生成图表的方式:
        1. PlotTimer周期自动生成: 数据文件在 Download/<ip>/TIMER_DATA/PLOTS 目录下
        2. 此方法临时生成: 数据文件在 Download/<ip>/目录下
        """
        # 如果download目录下有csv数据文件, 则使用该数据立即画图, 否则使用PlotTimer自动画的图 #
        for index, instance in enumerate(self.page_ctrl.get_plot_widgets()):
            data_file, params = instance.get_data_file_and_plot_params(ip)
            download_data = '{}\\{}\\{}'.format(view_gate.query_env_define_data('G_DOWNLOAD_DIR'), ip, data_file)
            if Common.is_file(download_data):
                view_gate.make_plot_pic_data.set_data((ip, download_data, params))
            img_path = view_gate.query_plot_image_path((ip, data_file))
            if img_path:
                img_key = '_PLOT_TEMP_%s_%s' % (ip, index)
                view_gate.add_image_data.set_data((img_key, img_path))
                img_data = view_gate.query_photo_image_data(img_key)
                self.tmp_img_keys.append(img_key)
            else:
                # 如果image文件不存在, 则plot控件中提示一下
                img_data = '%s\n%s, %s' % (data_file,
                                           language_data('FILE', 'DATA', 'NOT_EXIST'),
                                           language_data('PLS_TRY_LATER'))
            self.page_ctrl.update_plot_widgets(instance, ip, img_data)

    def login_instance_handle(self, data):
        if data[0] == 'NEW':
            NewLoginTop().show(self.new_login_handle, view_gate.query_default_pwd_data())
        else:  # 'DEL'
            ip = data[1]
            view_gate.del_login_data.set_data((ip,))
            self.printoutbar.delete_instance(ip)
            self.page_ctrl.remove_widgets_tab(ip)

    @classmethod
    def new_login_handle(cls, data):
        """ 新建登录逻辑处理 """
        if not all(data):
            WidgetTip.warn('IP {}'.format(language_data('CANT_NULL')))
            return
        ip, user, upwd, rpwd = data
        if not Common.is_ip(ip):
            WidgetTip.error('IP {}'.format(language_data('INVALID_PARAMETERS')))
            return
        if ip in view_gate.query_login_ip_data():
            WidgetTip.error('{} {}'.format(ip, language_data('DUPLICATE')))
            return
        view_gate.new_login_data.set_data(data)


class SelectionBar(object):
    """ IP 选项栏 """
    def __init__(self, master, ips, exec_callback, inst_callback):
        self.master = master
        self.exec_callback = exec_callback
        self.inst_callback = inst_callback
        self.ips_fm = None
        self.ip_instance = {}    # {IP: [tip, var, progress], ...}
        self.opt_instance = []   # [[var, en, timer_lab], ...]
        self.btn_instance = []
        self.pack(ips)

    def set_progress(self, ip, prog, status):
        if ip in self.ip_instance:
            self.ip_instance[ip][2].update(prog, status, False)

    def set_status(self, ip, status):
        if ip not in self.ip_instance:
            self.append_instance(ip)
        self.ip_instance[ip][0]['fg'] = Global.G_TIP_FG_COLOR[status]

    def disable_exec_button(self, sw):
        state = 'disabled' if sw else '!disabled'
        [btn.state([state]) for btn in self.btn_instance]

    def get_is_in_root(self):
        return True if int(self.opt_instance[0][0].get()) == 1 else False

    def disable_root_checkbox(self, be, be_not):
        state, var = '!disabled', 0
        if be:
            state = 'disabled'
            var = 1
        if be_not:
            state = 'disabled'
        self.opt_instance[0][0].set(var)
        self.opt_instance[0][1].state([state])

    def update_timer(self, left_sec):
        h = int(left_sec / 3600)
        m = int((left_sec % 3600) / 60)
        s = int(left_sec % 60)
        self.opt_instance[1][2]['text'] = '%02d:%02d:%02d' % (h, m, s)

    def append_instance(self, ip):
        def destroy(event=None):
            if not WidgetTip.ask('{}: {}'.format(language_data('CONFIRM', 'DELETE'), ip)):
                return
            if len(self.ip_instance) == 1:
                WidgetTip.error(language_data('AT_LEAST_ONE_IP'))
                return
            if ip in self.ip_instance:
                del self.ip_instance[ip]
            master.destroy()
            self.inst_callback(('DEL', ip))

        master = tk.Frame(self.ips_fm)
        master.pack()
        tip = tk.Label(master, text='●', font=(Global.G_DEFAULT_FONT, 12), fg=Global.G_TIP_FG_COLOR['DEFAULT'])
        tip.grid(row=1, column=0)
        ip_var = tk.IntVar()
        ttk.Checkbutton(master, text=ip, width=15, variable=ip_var).grid(row=1, column=1)
        prog = ProgressBar(master=master, width=180, row=1, column=2)
        tk.Button(master, text='x', font=(Global.G_DEFAULT_FONT, 10), bd=0, command=destroy).grid(row=1, column=3)

        self.ip_instance[ip] = []
        self.ip_instance[ip].append(tip)
        self.ip_instance[ip].append(ip_var)
        self.ip_instance[ip].append(prog)

    def execute(self, operate):
        ips, opts = [], []
        for ip, inst in self.ip_instance.items():
            if int(inst[1].get()):
                ips.append(ip)
        for i, inst in enumerate(self.opt_instance):
            if i == 0:
                opts.append(True if int(inst[0].get()) == 1 else False)
                continue
            val = 0
            if int(inst[0].get()) == 1:   # 选中复选框 #
                val = inst[1].get()
                if not val.isdigit():
                    WidgetTip.error("'{}' {}".format(language_data('PERIODIC_EXECUTE'),
                                                     language_data('INVALID_PARAMETERS')))
                    return
            opts.append(int(val))
        self.exec_callback((operate, 'start', ips, opts))

    def pack(self, ips):
        def pack_ips():
            def select_all(event=None):
                for _, inst in self.ip_instance.items():
                    inst[1].set(int(var.get()))

            def new_login(event=None):
                self.inst_callback(('NEW',))

            master = tk.LabelFrame(self.master)
            master.pack(fill='both')
            top = tk.Frame(master, width=width)
            top.pack(fill='x')
            var = tk.IntVar()
            ttk.Checkbutton(top, variable=var, command=select_all, text=language_data('SELECT_ALL')
                            ).pack(side='left', padx=20)
            tk.Button(top, image=view_gate.query_photo_image_data('TB_LOGIN'), bd=0, command=new_login
                      ).pack(side='right', padx=30)
            self.ips_fm = ScrollFrame(master).master()
            for ip in ips:
                self.set_status(ip, 'SUCCESS')

        def pack_opts():
            self.opt_instance, self.btn_instance = [], []
            opt_fm = tk.Frame(self.master, width=width, height=height / 3 / 3 * 2)
            btn_fm = tk.Frame(self.master, width=width, height=height / 3 / 3 * 1)
            opt_fm.pack(fill='both')
            btn_fm.pack(fill='both')
            # options #
            ckb_style = {'master': opt_fm, 'width': 17}
            v1, v2 = tk.IntVar(), tk.IntVar()
            chk1 = ttk.Checkbutton(text=language_data('ROOT_EXECUTE'), variable=v1, **ckb_style)
            chk1.grid(row=0, column=0, padx=20, pady=3)
            chk2 = ttk.Checkbutton(text=language_data('PERIODIC_EXECUTE'), variable=v2, **ckb_style)
            en2 = ttk.Entry(opt_fm, width=15)
            lab0 = tk.Label(opt_fm, text=language_data('SECONDS'))
            if not is_pure_mode():
                chk2.grid(row=1, column=0, padx=20, pady=3)
                en2.grid(row=1, column=1, padx=15)
                lab0.grid(row=1, column=2)
            # buttons #
            btn_e = ttk.Button(btn_fm, text=language_data('START'),
                               width=20, command=lambda: self.execute('START'))
            btn_e.grid(row=0, column=0, padx=15, pady=15, ipady=2)
            btn_s = ttk.Button(btn_fm, text=language_data('STOP'),
                               width=20, command=lambda: self.execute('STOP'))
            btn_s.grid(row=0, column=1, padx=15, pady=15, ipady=2)
            lab1 = tk.Label(self.master, text=language_data('NEXT_EXECUTE_COUNTDOWN'),
                            font=(Global.G_DEFAULT_FONT, 12))
            lab2 = tk.Label(self.master, text='00:00:00', font=(Global.G_DEFAULT_FONT, 15))
            if not is_pure_mode():
                lab1.pack()
                lab2.pack()
            self.opt_instance.append((v1, chk1))  # root执行实例
            self.opt_instance.append((v2, en2, lab2))   # 周期执行实例
            self.btn_instance.append(btn_e)
            self.btn_instance.append(btn_s)

        width, height = Global.G_MAIN_IPS_WIDTH, Global.G_MAIN_OPER_HEIGHT
        pack_ips()
        pack_opts()


class PrintOutBar(object):
    """ 弹窗结果栏 """
    def __init__(self, master, ips):
        self.master = master
        self.instance = {}
        self.notebook = None
        self.entry = None
        self.pack(ips)

    def delete_instance(self, ip):
        if ip in self.instance:
            self.notebook.forget(self.instance[ip][0])

    def append_instance(self, ip):
        fm = tk.Frame(self.notebook)
        fm.pack()
        text = scrolledtext.ScrolledText(fm, font=(Global.G_TEXT_FONT, 8), width=100, height=40, bg='Snow',
                                         stat='disabled')
        text.tag_configure('highlight', background='red')
        text.pack(fill='both')
        self.notebook.add(fm, text=ip)   # fm 作为 notebook的tab_id, 添加和删除时需要用到
        self.instance[ip] = (fm, text)

    def pack(self, ips):
        sub_fm = tk.Frame(self.master)
        sub_fm.pack()
        tk.Label(sub_fm, text=language_data('HIGHLIGHT_SEARCH'), font=(Global.G_DEFAULT_FONT, 10)
                 ).pack(side='left', padx=5)
        self.entry = ttk.Entry(sub_fm, width=45)
        self.entry.pack(side='left')
        self.entry.bind('<Key>', self.search)
        tags = self.entry.bindtags()
        self.entry.bindtags(tags[1:] + tags[:1])
        self.notebook = ttk.Notebook(self.master, style="App.TNotebook")
        self.notebook.pack()
        for ip in ips:
            self.append_instance(ip)

    def search(self, event=None):
        for ip, inst in self.instance.items():
            fm, text = inst
            text.tag_remove("highlight", "1.0", "end")
            start = 1.0
            while True:
                pattern = self.entry.get()
                if not pattern:
                    break
                pos = text.search(pattern, start, stopindex='end', regexp=True)
                if not pos:
                    break
                end = '%s.%s' % (pos.split('.')[0], len(pattern) + int(pos.split('.')[1]))
                text.tag_add('highlight', pos, end)
                start = pos + '+1c'

    def update(self, ip, info):
        if not self.master:
            return
        instance = self.instance[ip][1]
        instance['stat'] = 'normal'
        instance.delete('0.0', 'end')
        instance.insert('end', '{}\n'.format(info))
        instance.see('end')
        instance['stat'] = 'disabled'

